<?php
/** 
 *           File:  WsdlCode.php
 *           Path:  ./test/wsdl
 *         Author:  A-yu
 *       Modifier:  A-yu
 *       Modified:  2011-01-19 19:59:07  
 */
class WsdlCode
{
    /**
     * wsdl
     * 
     * @var string
     */
    public $wsdl = '';

    /**
     * serviceClassName
     * 
     * @var string
     */
    public $serviceClassName = '';

    /**
     * targetNamespace
     * 
     * @var string
     */
    public $targetNamespace = '';

    /**
     * documentation
     * 
     * @var array
     */
    public $documentation = array('service' => '', 'operations' => array());

    /**
     * operations
     * 
     * @var array
     */
    public $operations = array();

    /**
     * types
     * 
     * @var array
     */
    public $types = array();

    /**
     * __construct
     * 
     * @param mixed $wsdl 
     * @return void
     */
    public function __construct($wsdl)
    {
        $soapCacheIniValue = ini_get('soap.wsdl_cache_enabled');
        ini_set('soap.wsdl_cache_enabled', 0); // disable WSDL cache

        $soap = new SoapClient($wsdl);
        $xdom = DomDocument::load($wsdl);

        /** 
         * Set WSDL.
         */
        $this->wsdl = $wsdl;

        /** 
         * Set service class name.
         */
        $this->serviceClassName = $xdom->getElementsByTagNameNS('*', 'service')->item(0)->getAttribute('name');

        /** 
         * Set target namespace, can be used as uri.
         */
        foreach ($xdom->getElementsByTagName('definitions') as $node) {
            $this->targetNamespace = $node->getAttribute('targetNamespace');
            break;
        }

        /** 
         * Set documentation.
         */
        $this->documentation = array('service' => '', 'operations' => array());
        foreach ($xdom->getElementsByTagName('documentation') as $node)
        {
            if ($node->parentNode->localName == 'service')
            {
                $this->documentation['service'] = trim($node->parentNode->nodeValue);
            }
            else if ($node->parentNode->localName == 'operation')
            {
                $this->documentation['operations'][$node->parentNode->getAttribute('name')] = trim($node->nodeValue);
            }
        }

        /** 
         * Set operations.
         */
        foreach ($soap->__getFunctions() as $operation)
        {
            if (preg_match('/^(\w[\w\d_]*) (\w[\w\d_]*)\(([\w\$\d,_ ]*)\)$/', $operation, $matches) ||
                preg_match('/^(list\([\w\$\d,_ ]*\)) (\w[\w\d_]*)\(([\w\$\d,_ ]*)\)$/', $operation, $matches))
            {
                $response = $matches[1];
                $function = $matches[2];
                $argument = $matches[3];

                $argument = explode(', ', $argument);
                foreach ($argument as $idx => $val)
                {
                    $argument[$idx] = array();
                    list($argument[$idx]['type'], $argument[$idx]['name']) = explode(' ', $val);
                }

                $this->operations[$function] = array(
                    'signature' => $operation,
                    'function'  => $function,
                    'response'  => $response,
                    'argument'  => $argument,
                );
            }
            else
            {
                // throw new Exception("Invalid function call: {$operation}");
            }
        }

        /** 
         * Set types.
         */
        foreach ($soap->__getTypes() as $type)
        {
            /** 
             * Type example:
             *    struct ClassName {
             *    SubClass className;
             *    string sName;
             *    int nId;
             *   }
             */
            $parts = explode("\n", $type);
            $class = explode(' ', $parts[0]);
            $class = $class[1];

            /** 
             * Skip: array, 'ArrayOf*' types (from MS.NET, Axis etc.)
             */
            if (substr($class, -2, 2) == '[]' || substr($class, 0, 7) == 'ArrayOf')
            {
                continue;
            }

            $members = array();
            for($i=1; $i<count($parts)-1; $i++)
            {
                list($type, $name) = explode(' ', substr(trim($parts[$i]), 0, -1));

                /** 
                 * Check syntax.
                 */
                if (preg_match('/^$\w[\w\d_]*$/', $name))
                {
                    // throw new Exception('illegal syntax for member variable: ' . $name);
                    continue;
                }

                /** 
                 * Filter out namespace
                 */
                if (($pos = strpos($name, ':')) !== false)
                {
                    $name = substr($name, $pos);
                }

                /** 
                 * Add member once.
                 */
                array_key_exists($name, $members) OR $members[$name] = array('type' => $type, 'name' => $name);
            }

            /** 
             * Look for extends.
             */
            $extends = array();
            $extension = $xdom->getElementsByTagName('extension');
            for ($i = 0; $i < $extension->length; $i++)
            {
                $subc = $extension->item($i)->parentNode->parentNode->attributes->getNamedItem('name')->nodeValue;
                $base = $extension->item($i)->attributes->getNamedItem('base')->nodeValue;

                /** 
                 * Strip namespace.
                 */
                $base = explode(':', $base);
                is_array($base) AND $base = array_pop($base);

                $extends[$subc] = $base;
            }

            /** 
             * Look for enumeration.
             */
            $values = array();
            if (empty($members))
            {
                $schemas = $xdom->getElementsByTagName('types')->item(0)->getElementsByTagName('schema');

                for ($i=0; $i<$schemas->length; $i++)
                {
                    $children = $schemas->item($i)->childNodes;
                    for ($j=0; $j<$children->length; $j++)
                    {
                        $node = $children->item($j);
                        if ($node instanceof DOMElement &&
                            $node->hasAttributes() &&
                            $node->attributes->getNamedItem('name') !== null &&
                            $node->attributes->getNamedItem('name')->nodeValue == $class)
                        {
                            $enumerations = $node->getElementsByTagName('enumeration');
                            for ($i=0; $i<$enumerations->length; $i++)
                            {
                                $values[] = $enumerations->item($i)->attributes->getNamedItem('value')->nodeValue;
                            }

                            break 2;
                        }
                    }
                }
            }

            $this->types[$class] = array('class' => $class, 'members' => $members, 'values' => $values, 'extends' => '');
            array_key_exists($class, $extends) AND $this->types[$class]['extends'] = $extends[$class];
        }

        ini_set('soap.wsdl_cache_enabled', $soapCacheIniValue);
    }

    /**
     * __toString
     * 
     * @return string
     */
    public function __toString()
    {
        $code = '<?php ' . PHP_EOL;

        $code .= "/**" . PHP_EOL;
        $code .= " * {$this->serviceClassName}" . PHP_EOL;
        empty($this->documentation['service']) OR $code .= " * {$this->documentation['service']}" . PHP_EOL;
        $code .= " * " . PHP_EOL;
        $code .= " */" . PHP_EOL;
        $code .= "class {$this->serviceClassName}" . PHP_EOL;
        $code .= "{" . PHP_EOL;

        $code .= "    /**" . PHP_EOL;
        $code .= "     * _client" . PHP_EOL;
        $code .= "     * " . PHP_EOL;
        $code .= "     * @param SoapClient" . PHP_EOL;
        $code .= "     */" . PHP_EOL;
        $code .= "    protected \$_client;" . PHP_EOL;
        $code .= PHP_EOL;

        $code .= "    /**" . PHP_EOL;
        $code .= "     * _header" . PHP_EOL;
        $code .= "     * " . PHP_EOL;
        $code .= "     * @param SoapHeader" . PHP_EOL;
        $code .= "     */" . PHP_EOL;
        $code .= "    protected \$_header;" . PHP_EOL;
        $code .= PHP_EOL;

        $code .= "    /**" . PHP_EOL;
        $code .= "     * __construct" . PHP_EOL;
        $code .= "     * " . PHP_EOL;
        $code .= "     * @param string \$wsdl" . PHP_EOL;
        $code .= "     * @param array  \$args" . PHP_EOL;
        $code .= "     * @return void" . PHP_EOL;
        $code .= "     */" . PHP_EOL;
        $code .= "    public function __construct(\$wsdl = '{$this->wsdl}', array \$args = array())" . PHP_EOL;
        $code .= "    {" . PHP_EOL;
        $code .= "        \$this->_client = new SoapClient(\$wsdl, \$args);" . PHP_EOL;
        $code .= "    }" . PHP_EOL;
        $code .= PHP_EOL;

        $code .= "    /**" . PHP_EOL;
        $code .= "     * call" . PHP_EOL;
        $code .= "     * " . PHP_EOL;
        $code .= "     * @param string \$func" . PHP_EOL;
        $code .= "     * @param array  \$args" . PHP_EOL;
        $code .= "     * @return mixed" . PHP_EOL;
        $code .= "     */" . PHP_EOL;
        $code .= "    public function call(\$func, array \$args = array())" . PHP_EOL;
        $code .= "    {" . PHP_EOL;
        $code .= "        return \$this->_client->__soapCall(\$func, \$args, array('uri' => '{$this->targetNamespace}'), \$this->_header);" . PHP_EOL;
        $code .= "    }" . PHP_EOL;
        $code .= PHP_EOL;

        foreach ($this->operations as $operation)
        {
            $code .= "    /**" . PHP_EOL;
            $code .= "     * {$operation['function']}" . PHP_EOL;
            if (array_key_exists($operation['function'], $this->documentation['operations']))
            {
                $code .= "     * {$this->documentation['operations'][$operation['function']]}" . PHP_EOL;
            }
            $code .= "     * " . PHP_EOL;

            foreach ($operation['argument'] as $val)
            {
                $code .= "     * @param {$val['type']} {$val['name']}" . PHP_EOL;
            }

            $code .= "     * @return {$operation['response']}" . PHP_EOL;
            $code .= "     */" . PHP_EOL;

            $code .= "    public function {$operation['function']}(";
            foreach ($operation['argument'] as $val)
            {
                $code .= "{$val['name']}, ";
            }
            $code = rtrim($code, ', ');
            $code .= ")" . PHP_EOL;

            $code .= "    {" . PHP_EOL;
            $code .= "        \$args = get_defined_vars();" . PHP_EOL;
            $code .= "        return \$this->call(__FUNCTION__, \$args);" . PHP_EOL;
            $code .= "    }" . PHP_EOL;
            $code .= PHP_EOL;
        }

        $code = rtrim($code) . PHP_EOL;
        $code .= "}" . PHP_EOL;
        $code .= PHP_EOL;

        foreach($this->types as $type)
        {
            $code .= "class {$type['class']}";
            empty($type['extends']) OR $code .= ' extends ' . $type['extends'];
            $code .=  PHP_EOL;
            $code .= "{" . PHP_EOL;

            /** 
             * Add enumeration values
             */
            foreach ($type['values'] as $value)
            {
                $code .= "    const $value = '$value';" . PHP_EOL;
            }

            /** 
             * Add member variables
             */
            foreach ($type['members'] as $member)
            {
                $code .= "    /**" . PHP_EOL;
                $code .= "     * {$member['name']}" . PHP_EOL;
                $code .= "     * " . PHP_EOL;
                $code .= "     * @var {$member['type']}" . PHP_EOL;
                $code .= "     */" . PHP_EOL;
                $code .= "    public \${$member['name']};" . PHP_EOL;
                $code .= PHP_EOL;
            }

            $code = rtrim($code) . PHP_EOL;
            $code .= "}" . PHP_EOL;
            $code .= PHP_EOL;
        }

        return $code;
    }

    /**
     * getAllMemberByTypeName
     * 
     * @return array
     */
    public function getAllMemberByTypeName($name)
    {
        $typeMembers = $this->types[$name]['members'];

        if (!empty($this->types[$name]['extends']))
        {
            if (array_key_exists($this->types[$name]['extends'], $this->types))
            {
                $parent = $this->types[$this->types[$name]['extends']];
                $typeMembers = array_merge($parent['members'], $typeMembers); // Merge parent members.

                if (!empty($parent['extends'])) // Continue to look for parent members.
                {
                    $typeMembers = array_merge($this->getAllMemberByTypeName($parent['extends']), $typeMembers);
                }
            }
        }

        return $typeMembers;
    }
}
// End of file : WsdlCode.php
